/*
 * Students:
 * Massimiliano Rango - 610826
 * Martina Benedetto - 610962
 * 
 * A.A 2011/2012
 * 
 */ 

import java.util.Vector;
import java.util.Random;

/**
 *
 * This class is a thread which represents the hypervisor of ONE simulation. 
 * When an Hypervisor object starts its run, it creates the network with the 
 * given parameters from a Configuration object. Then he provides to create a
 * clone in the network and to start the nodes. When the simulation has ended,
 * the Hypervisor collects some information about the simulation and saves them
 * in the provided fields.
 * 
 * @version 1.0 27 Dec 2011
 * @author Massimiliano Rango
 *
 */
public class Hypervisor extends Thread {
	
	private Configuration m_conf;
	private Vector<Node> m_network;
	private boolean m_isCompleted;
	
	private int minSM;	/* SM = SENT MESSAGES */		
	private int maxSM;	
	private double avgSM;
	private double sdSM; 	
	private int minRM;	/* RM = RECEIVED MESSAGES */
	private int maxRM;
	private double avgRM;
	private double sdRM;
	private int minSIG;	/* SIG = SIGNATURES */
	private int maxSIG;
	private double avgSIG;
	private double sdSIG;
	private int minE;	/* E = SPENT ENERGY */
	private int maxE;
	private double avgE;
	private double sdE;
	private int minMA;	/* MA=MESSAGES IN ARCHIVE */
	private int maxMA;
	private double avgMA;
	private double sdMA;
	
	/**
	 * Creates a new Hypervisor with the given Configuration object
	 * @param conf Contains the parameters of the simulation
	 */
	public Hypervisor(Configuration conf) {
		m_conf = conf;
		m_network = new Vector<Node>();
		m_isCompleted = false;
	}
	
	/**
	 * Creates a Position with two float random values between 0 and 1
	 * @return A random Position value
	 */
	public Position getRandomPos() { 
		Random rand_p = new Random();
		float x = rand_p.nextFloat(); 
		float y = rand_p.nextFloat();
		Position pos = new Position(x,y);
		return pos;
	}
	
	/**
	 * Adds a node with the same ID of another random node to the network 
	 * vector.
	 */
	private void createClone() {
		Random rand_c = new Random();
		int cloneId = rand_c.nextInt(m_conf.getNNodes());
		Position whereClone = getRandomPos();
		Node nClone;
		if (m_conf.getProtocol() == Configuration.Protocol.RED) {
			nClone = new REDNode(cloneId, whereClone, m_conf);
		} else { 
			nClone = new LSMNode(cloneId, whereClone, m_conf);
		}
		m_network.add(nClone);
	}
	
	/**
	 * Creates a network consisting of n nodes, where n is a number
	 * specified in the configuration file.
	 */
	private void createNetwork() {
		int nNodes = m_conf.getNNodes();
		if (m_conf.getProtocol() == Configuration.Protocol.RED) {
			for (int i = 0; i < nNodes; i++) {
				Position where = getRandomPos();
				REDNode n = new REDNode (i, where, m_conf);
			    m_network.add(n);
			}
			Random generator = new Random();
			int rand = generator.nextInt();
			m_conf.setRand(rand);
		} else if (m_conf.getProtocol() == Configuration.Protocol.LSM) {
			for (int i = 0; i < nNodes; i++) {
				Position where = getRandomPos();
				LSMNode n = new LSMNode (i, where, m_conf);
				m_network.add(n);
			}
		}
		createClone();
	}
	
	/**
	 * Indicates if current is neighbour of test.
	 * @param current The position of the node whose neighbours we want to 
	 * find.
	 * @param test The position of the node we have to check if it is one of 
	 * current's neighbours or not.
	 * @return Indication if current is neighbour of test.
	 */
	private boolean isNeighbour(Position current, Position test) {
		float x_current = current.getXPosition();
		float y_current = current.getYPosition();
		float x_test = test.getXPosition();
		float y_test = test.getYPosition();
		return Math.sqrt(Math.pow((x_current - x_test), 2) 
				+ Math.pow((y_current-y_test), 2)) 
				<= m_conf.getRadius();
	}
	
	/**
	 * Sets the neighbours of each node in the network.
	 */
	private void neighbourhood() {
		for (int i = 0; i < m_network.size(); i++) { 
			Node current = m_network.get(i);
			Position pCurrent = current.getPosition();
			for (int j = 0; j < m_network.size(); j++) {
				Node test = m_network.get(j);
				if (current != test) { 
					
					/* a node is not neighbour of himself */
					Position pTest = test.getPosition();
					if (isNeighbour(pCurrent, pTest)) {
						current.addNeighbour(test);
					}
				}
			}
		}
	}
	
	/**
	 * Calculates minimum, maximum, average and standard deviation of the
	 * number of sent messages in this simulation. 
	 */
	private void sentMessagesStats() {
		minSM = m_network.get(0).getSentMessages();
		maxSM = m_network.get(0).getSentMessages();
		avgSM = m_network.get(0).getSentMessages();
		sdSM = 0;
		
		for (int i = 1; i < m_network.size(); i++) {
			if (m_network.get(i).getSentMessages() < minSM) {
				minSM = m_network.get(i).getSentMessages();
			}
			if (m_network.get(i).getSentMessages() > maxSM) {
				maxSM = m_network.get(i).getSentMessages();
			}
			avgSM += m_network.get(i).getSentMessages();
		}
		avgSM /= m_network.size();
		
		for (int i = 0; i < m_network.size(); i++) {
			sdSM += Math.pow((m_network.get(i).getSentMessages() - avgSM), 2);
		}
		sdSM = Math.sqrt(sdSM / m_network.size());
	}
	
	/**
	 * Calculates minimum, maximum, average and standard deviation of the
	 * number of received messages in this simulation. 
	 */
	private void receivedMessagesStats() {
		minRM = m_network.get(0).getReceivedMessages();
		maxRM = m_network.get(0).getReceivedMessages();
		avgRM = m_network.get(0).getReceivedMessages();
		sdRM = 0;
		
		for (int i = 1; i < m_network.size(); i++) {
			if(m_network.get(i).getReceivedMessages() < minRM) {
				minRM = m_network.get(i).getReceivedMessages();
			}
			if (m_network.get(i).getReceivedMessages() > maxRM) {
				maxRM = m_network.get(i).getReceivedMessages();
			}
			avgRM += m_network.get(i).getReceivedMessages();
		}
		avgRM /= m_network.size();
		
		for (int i = 0; i < m_network.size(); i++) {
			sdRM += Math.pow((m_network.get(i).getReceivedMessages() - avgRM), 2);
		}
		sdRM = Math.sqrt(sdRM / m_network.size());
	}
	
	/**
	 * Calculates minimum, maximum, average and standard deviation of the
	 * number of signature checks in this simulation. 
	 */
	private void signaturesStats() {
		minSIG = m_network.get(0).getSignatures();
		maxSIG = m_network.get(0).getSignatures();
		avgSIG = m_network.get(0).getSignatures();
		sdSIG = 0;
		
		for (int i = 1; i < m_network.size(); i++) {
			if(m_network.get(i).getSignatures() < minSIG) {
				minSIG = m_network.get(i).getSignatures();
			}
			if (m_network.get(i).getSignatures() > maxSIG) {
				maxSIG = m_network.get(i).getSignatures();
			}
			avgSIG += m_network.get(i).getSignatures();
		}
		avgSIG /= m_network.size();
		
		for (int i = 0; i < m_network.size(); i++) {
			sdSIG += Math.pow((m_network.get(i).getSignatures() - avgSIG), 2);
		}
		sdSIG = Math.sqrt(sdSIG / m_network.size());
	}
	
	/**
	 * Calculates minimum, maximum, average and standard deviation of spent
	 * energy in this simulation. 
	 */
	private void spentEnergyStats() {
		int tE = m_conf.getTotEnergy();
		minE = m_conf.getTotEnergy() - m_network.get(0).getRemainingEnergy();
		maxE = m_conf.getTotEnergy() - m_network.get(0).getRemainingEnergy();
		avgE = (m_conf.getTotEnergy() - m_network.get(0).getRemainingEnergy());
		sdE = 0;
		
		for (int i = 1; i < m_network.size(); i++) {
			if((tE - m_network.get(i).getRemainingEnergy()) < minE) {
				minE = tE - m_network.get(i).getRemainingEnergy();
			}
			if ((tE - m_network.get(i).getRemainingEnergy()) > maxE) {
				maxE = tE - m_network.get(i).getRemainingEnergy();
			}
			avgE += (tE - m_network.get(i).getRemainingEnergy());
		}
		avgE /= m_network.size();
		
		for (int i = 0; i < m_network.size(); i++) {
			sdE += Math.pow(((tE - m_network.get(i).getRemainingEnergy()) - avgE), 2);
		}
		sdE = Math.sqrt(sdE / m_network.size());
	}
	
	/**
	 * Calculates minimum, maximum, average and standard deviation of the
	 * number of memorized messages in this simulation. 
	 */
	private void archiveMessagesStats() {
		minMA = m_network.get(0).sizeArchive();
		maxMA = m_network.get(0).sizeArchive();
		avgMA = m_network.get(0).sizeArchive();
		sdMA = 0;
		
		for (int i = 1; i < m_network.size(); i++) {
			if (m_network.get(i).sizeArchive() < minMA) {
				minMA = m_network.get(i).sizeArchive();
			}
			if (m_network.get(i).sizeArchive() > maxMA) {
				maxMA = m_network.get(i).sizeArchive();
			}
			avgMA += m_network.get(i).sizeArchive();
		}
		avgMA /= m_network.size();
		
		for (int i = 0; i < m_network.size(); i++) {
			sdMA += Math.pow((m_network.get(i).sizeArchive() - avgMA), 2);
		}
		sdMA = Math.sqrt(sdMA / m_network.size());
	}
	
	public Configuration getConf(){
		return m_conf;
	}
	
	public int getminSM(){
		return minSM;
	}
	
	public int getmaxSM(){
		return maxSM;
	}
	
	public double getavSM(){
		return avgSM;
	}
	
	public double getsdSM(){
		return sdSM;
	}
	
	public int getminRM(){
		return minRM;
	}
	
	public int getmaxRM(){
		return maxRM;
	}
	
	public double getavRM(){
		return avgRM;
	}
	
	public double getsdRM(){
		return sdRM;
	}
	
	public int getminSIG(){
		return minSIG;
	}
	
	public int getmaxSIG(){
		return maxSIG;
	}
	
	public double getavSIG(){
		return avgSIG;
	}
	
	public double getsdSIG(){
		return sdSIG;
	}
	
	public int getminE(){
		return minE;
	}
	
	public int getmaxE(){
		return maxE;
	}
	
	public double getavE(){
		return avgE;
	}
	
	public double getsdE(){
		return sdE;
	}
	
	public int getminMA(){
		return minMA;
	}
	
	public int getmaxMA(){
		return maxMA;
	}
	
	public double getavMA(){
		return avgMA;
	}
	
	public double getsdMA(){
		return sdMA;
	}
	
	/**
	 * Indicates if this simulation has finished
	 * @return Indication if this simulation has finished
	 */
	public boolean isCompleted() {
		return m_isCompleted;
	}
	
	public void run() {
		try {
			
			createNetwork();
			neighbourhood();
			
			for (int i = 0; i < m_network.size(); i++) {
				m_network.get(i).start();
			}
			int totNodes = m_conf.getNNodes() + 1;
			synchronized (m_conf.lockEndSimulation) {
				
				/*
				 *  
				 * While there are active nodes AND (someone 
				 * has not sent his location claims yet OR the 
				 * simulation has not ended) 
				 * 
				 */
				while (m_conf.getKilledNodes() < totNodes 
						&& (m_conf.getNodeLocClaim() < totNodes 
						|| (!m_conf.getCloneFound() 
						&& m_conf.getCirculatingMess() > 0))) {
					m_conf.lockEndSimulation.wait();
				}
			}
			
			for (int i = 0; i < m_network.size(); i++) {
				synchronized (m_network.get(i).lockIncoming) {
					m_network.get(i).lockIncoming.notifyAll();
				}
			}
			
			synchronized (m_conf.lockEndSimulation) {
				while (m_conf.getKilledNodes() < totNodes) {
					m_conf.lockEndSimulation.wait();
				}
			}
			
			/* Stats to send to RMI server */
			sentMessagesStats();
			receivedMessagesStats();
			signaturesStats();
			spentEnergyStats();
			archiveMessagesStats();
			
			synchronized (this) {
				m_isCompleted = true;
				notifyAll();
			}
		} catch (InterruptedException e) {
			return;
		}
	}
}